﻿/*
 * This program written by: Marek Otulakowski (marek.otulakowski@gmail.com)
 * last update: 19 december 2009
 * licence GNU v.2
 * homepage project http://mcopy.codeplex.com
*/

#region UsingDirectives
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO; 
#endregion

namespace mcopy
{
    /// <summary>
    /// Main class program
    /// <br />
    /// example use: <br />
    /// (bellow copy all files and save longFile to file log) <br />
    /// mcopy.exe "\\serwer\sourceFolder" "c:\destinationFolder" c:\log.txt m n<br />
    /// or <br />
    /// (bellow copy all files and save longFile to file log and save all copy operates to file log too) <br />
    /// mcopy.exe "\\serwer\sourceFolder" "c:\destinationFolder" c:\log.txt m n > "c:\resultCopy.txt" <br />
    /// ATTENTION: first argument must be shared folder (requred to copy long files)
    /// </summary>
    public class Program
    {
        /// <summary>
        /// path to folder where save file with long fullPathes
        /// </summary>
        static string folderDestinationRoot = string.Empty;

        /// <summary>
        /// path to folder where save source folder
        /// </summary>
        static string folderSourceRoot = string.Empty;

        /// <summary>
        /// temporary letter drive to copy long names
        /// </summary>
        static string chooseTempNetDrive = string.Empty;

        /// <summary>
        /// temporary letter drive to copy long names 2 (for 2 x 260 char)
        /// </summary>
        static string chooseTemp2NetDrive = string.Empty;

        /// <summary>
        /// short fullName requre to split long fullName (to display and to save to log) 
        /// </summary>
        static string maxFull1recursiveName = string.Empty;

        /// <summary>
        /// Main program
        /// </summary>
        /// <param name="args">requre 1 or 5 paramets</param>
        static void Main(string[] args)
        {
            if (args.Length == 5)
            {
                DirectoryInfo sourceDir = new DirectoryInfo(args[0]);
                DirectoryInfo destinationDir = new DirectoryInfo(args[1]);
                string pathToLog = args[2];
                chooseTempNetDrive = args[3].ToString();
                chooseTemp2NetDrive = args[4].ToString();

            ////bellow (7 line) only test
            //if (true)
            //{
            //    DirectoryInfo sourceDir = new DirectoryInfo(@"\\pc-marek\dane");
            //    DirectoryInfo destinationDir = new DirectoryInfo("e:\\daneD6");
            //    string pathToLog = "e:\\log.txt";
            //    chooseTempNetDrive = "M";
            //    chooseTemp2NetDrive = "N";

                folderDestinationRoot = destinationDir.ToString();
                folderSourceRoot = sourceDir.ToString();

                try
                {
                    DateTime startTime = DateTime.Now;
                    Console.WriteLine("Start program -> " + startTime.ToString());
                    Console.WriteLine("Don't break program, becouse program is running...");
                    CopyDirectory(sourceDir, destinationDir, pathToLog);
                    Console.WriteLine("Copy successfull :)");
                    DateTime finishTime = DateTime.Now;
                    Console.WriteLine("Finish program -> " + finishTime.ToString());
                    Console.WriteLine("All copy time -> " + (finishTime - startTime).ToString());

                    ////bellow line only test
                    //Console.ReadLine();
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Error 001 :( (Main function)" + ex.Message);
                    UnmountAllTemporaryDisc();
                }
            }
            else if (args.Length == 1)
            {
                if (args[0].ToString() == "/?")
                {
                    #region DisplayHelpInfo
                    Console.WriteLine();
                    Console.WriteLine("================================================================================");
                    Console.WriteLine("program written by: Marek Otulakowski (marek.otulakowski@gmail.com) last update: 19 december 2009, homepage project http://mcopy.codeplex.com");
                    Console.WriteLine("help v.2");
                    Console.WriteLine();
                    Console.WriteLine("example use:");
                    Console.WriteLine();
                    Console.WriteLine("(bellow copy all files and save longFile to file log)");
                    Console.WriteLine("mcopy.exe \"\\\\serwer\\sourceFolder\" \"c:\\destinationFolder\" c:\\log.txt m n");
                    Console.WriteLine("where \"m\" and \"n\" are temporary letter drive");
                    Console.WriteLine();
                    Console.WriteLine("or");
                    Console.WriteLine();
                    Console.WriteLine("(bellow copy all files and save longFile to file log and save all copy operates to file log too)");
                    Console.WriteLine("mcopy.exe \"\\\\serwer\\sourceFolder\" \"c:\\destinationFolder\" c:\\log.txt m n > \"c:\\destinationFolder\\resultCopy.txt");
                    Console.WriteLine("where \"m\" and \"n\" are temporary letter drive");
                    Console.WriteLine();
                    Console.WriteLine("ATTENTION: first argument must be shared folder (requred to copy long files)");
                    Console.WriteLine("================================================================================");
                    Console.WriteLine(); 
                    #endregion
                    Console.Read();
                }
                else
                {
                    Console.WriteLine("Didn't entered correctly arguments (source, destination, path to log, temporary letter drive, second temporary letter drive) or (/?)");
                }
            }
            else
            {
                Console.WriteLine("Didn't entered correctly arguments (source, destination, path to log, temporary letter drive, second temporary letter drive) or (/?)");
            }
        }

        /// <summary>
        /// function unmount all temporary disc
        /// after error exist
        /// </summary>
        static void UnmountAllTemporaryDisc()
        {
            RemoveNetworkDrive(chooseTempNetDrive);
            RemoveNetworkDrive(chooseTemp2NetDrive);
        }

        /// <summary>
        /// SaveToLog
        /// </summary>
        /// <remarks>
        /// function write messages to log
        /// </remarks>
        /// <param name="message">message to write</param>
        /// <param name="path">path to log file</param>
        static void SaveToLog(string message, string path)
        {
            // Create a writer and open the file:
            StreamWriter log;

            if (!File.Exists(path))
            {
                log = new StreamWriter(path);
            }
            else
            {
                log = File.AppendText(path);
            }

            // Write to the file:
            log.WriteLine(message);

            // Close the stream:
            log.Close();
        }

        /// <summary>
        /// function adding network mapping drive
        /// </summary>
        /// <param name="letterDrive">letter drive</param>
        /// <param name="pathToFolder">path to shares network folder</param>
        /// <returns>true if successfull mapping network letter</returns>
        static bool AddNetwordDrive(string letterDrive, string pathToFolder)
        {
            bool result = false;
            try
            {
                System.Diagnostics.ProcessStartInfo psi = new System.Diagnostics.ProcessStartInfo();
                psi.FileName = "net";
                psi.Arguments = "use " + letterDrive + ": \"" + pathToFolder + "\"";
                psi.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
                System.Diagnostics.Process p = System.Diagnostics.Process.Start(psi);
                p.WaitForExit();
                result = true;
            }
            catch (Exception ex)  
            {
                Console.WriteLine("Error 002 :( (Adding netword drive)" + ex.Message);
                UnmountAllTemporaryDisc();
            }
            return result;
        }

        /// <summary>
        /// function remove network temporary letterdrive
        /// </summary>
        /// <param name="letterDrive">letter drive to remove</param>
        /// <returns>true if remove letter drive succesfully</returns>
        static bool RemoveNetworkDrive(string letterDrive)
        {
            bool result = false;
            try
            {
                System.Diagnostics.ProcessStartInfo psi = new System.Diagnostics.ProcessStartInfo();
                psi.FileName = "net";
                psi.Arguments = "use " + letterDrive + ": /delete";
                psi.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
                System.Diagnostics.Process p = System.Diagnostics.Process.Start(psi);
                p.WaitForExit();
                result = true;
            }
            catch (Exception ex) 
            {
                Console.WriteLine("Error 003 :( (removing network drive)" + ex.Message);
                UnmountAllTemporaryDisc();
            }
            return result;
        }

        //function from http://support.microsoft.com/kb/320348
        /// <summary>
        /// function FileCompare
        /// </summary>
        /// <param name="file1"></param>
        /// <param name="file2"></param>
        /// <returns></returns>
        private static bool FileCompare(string file1, string file2)
        {
            int file1byte;
            int file2byte;
            FileStream fs1;
            FileStream fs2;

            // Determine if the same file was referenced two times.
            if (file1 == file2)
            {
                // Return true to indicate that the files are the same.
                return true;
            }

            // Open the two files.
            fs1 = new FileStream(file1, FileMode.Open);
            fs2 = new FileStream(file2, FileMode.Open);

            // Check the file sizes. If they are not the same, the files 
            // are not the same.
            if (fs1.Length != fs2.Length)
            {
                // Close the file
                fs1.Close();
                fs2.Close();

                // Return false to indicate files are different
                return false;
            }

            // Read and compare a byte from each file until either a
            // non-matching set of bytes is found or until the end of
            // file1 is reached.
            do
            {
                // Read one byte from each file.
                file1byte = fs1.ReadByte();
                file2byte = fs2.ReadByte();
            }
            while ((file1byte == file2byte) && (file1byte != -1));

            // Close the files.
            fs1.Close();
            fs2.Close();

            // Return the success of the comparison. "file1byte" is 
            // equal to "file2byte" at this point only if the files are 
            // the same.
            return ((file1byte - file2byte) == 0);
        }

        /// <summary>
        /// CopyDirectory
        /// </summary>
        /// <remarks>
        /// function to copy folder
        /// </remarks>
        /// <param name="source">path to source folder</param>
        /// <param name="destination">path to destination folder</param>
        /// <param name="pathToLog">path to log file</param>
        static void CopyDirectory(DirectoryInfo source, DirectoryInfo destination, string pathToLog)
        {
            if (!destination.Exists)
            {
                if (destination.FullName.Length < 248)
                {
                    //create folder (subfolder)
                    destination.Create();
                }
                else
                {
                    Console.WriteLine("NOT COPY! " + destination.FullName +
                                      "(" +
                                      destination.FullName.Length.ToString() +
                                      " chars)", pathToLog);
                    SaveToLog("NOT COPY! " + destination.FullName +
                              "(" +
                              destination.FullName.Length.ToString() +
                              " chars)", pathToLog);
                }
            }

            // Copy all files.
            FileInfo[] files = source.GetFiles();
            foreach (FileInfo file in files)
            {                
                if (File.Exists(Path.Combine(destination.FullName, file.Name)))
                {
                    string oldFile = Path.Combine(destination.FullName, file.Name);
                    string newFile = Path.Combine(source.FullName, file.Name);

                    if (!FileCompare(oldFile, newFile))
                    {
                        File.Delete(Path.Combine(destination.FullName, file.Name));
                    }
                    else
                    {
                        continue;
                    }
                }
                if ((Path.Combine(destination.FullName, file.Name).Length < 256))
                {
                    //create file
                    file.CopyTo(Path.Combine(destination.FullName, file.Name));
                    Console.WriteLine(Path.Combine(source.FullName, file.Name) +
                                      " -> " +
                                      Path.Combine(destination.FullName, file.Name));               
                }
                else
                {
                    RemoveNetworkDrive(chooseTempNetDrive);
                    if (AddNetwordDrive(chooseTempNetDrive, source.FullName))
                    {
                        try
                        {
                            string folderName = folderDestinationRoot + "\\_toLong";
                            if (!Directory.Exists(folderName))
                            {
                                Directory.CreateDirectory(folderName);
                            }
                            if (Path.Combine(folderName, file.Name).Length < 260)
	                        {
                                if (File.Exists(Path.Combine(folderName, file.Name)))
                                {
                                    string oldFile = Path.Combine(destination.FullName, file.Name);
                                    string newFile = Path.Combine(source.FullName, file.Name);

                                    if (!FileCompare(oldFile, newFile))
                                    {
                                        File.Delete(Path.Combine(destination.FullName, file.Name));
                                    }
                                    else
                                    {
                                        continue;
                                    }
                                }
                                File.Copy(chooseTempNetDrive + ":\\" + file.Name,
                                          Path.Combine(folderName, file.Name));
                                Console.WriteLine(chooseTempNetDrive + ":\\" + file.Name +
                                                  " -> " +
                                                  Path.Combine(folderName, file.Name));
                                SaveToLog("From " + source.FullName + "\\" + file.Name + " -> copy file to -> " + Path.Combine(folderDestinationRoot + "\\_toLong\\" + file.Name, file.Name), folderDestinationRoot + "\\_toLong\\_logLongFile.txt"); 
	                        }
                        }
                        catch (Exception ex)
                        {
                            Console.Write("Error 004 :( (copy long file)" + ex.Message);
                            SaveToLog("error: " + Path.Combine(source.FullName, file.Name) +
                                      "(" +
                                      Path.Combine(source.FullName, file.Name).Length.ToString() +
                                      " chars)", pathToLog);
                            UnmountAllTemporaryDisc();
                        }
                        RemoveNetworkDrive(chooseTempNetDrive);
                    }
                    else
                    {
                        Console.Write("DON'T COPY!!!!!!!!!!!!, file name is too long ->\n");
                        Console.WriteLine(Path.Combine(source.FullName, file.Name) +
                                          " (" +
                                          (Path.Combine(source.FullName, file.Name)).Length.ToString() +
                                          " chars)");
                        SaveToLog(Path.Combine(source.FullName, file.Name) +
                                  "(" +
                                  Path.Combine(source.FullName, file.Name).Length.ToString() +
                                  " chars)", pathToLog);
                    }
                }
            }

            // Process subdirectories.
            DirectoryInfo[] dirs = source.GetDirectories();
            foreach (DirectoryInfo dir in dirs)
            {
                // Get destination directory.
                string destinationDir = Path.Combine(destination.FullName, dir.Name);

                if (destinationDir.Length > 247)
                {
                    try
                    {
                        //limition path "temporary :( I think so!"
                        if (dir.Root.Name == (chooseTemp2NetDrive + ":\\"))
                        {
                            Console.WriteLine("limit count fullpath program mcopy, application will exit");
                            System.Environment.Exit(-1);
                        }

                        string folderSaveToLongFile = folderDestinationRoot + "\\_toLong";

                        if (!Directory.Exists(folderSaveToLongFile))
                        {
                            Directory.CreateDirectory(folderSaveToLongFile);
                        }
                        string nameLongCatalog = destinationDir.Substring(destinationDir.LastIndexOf("\\") + 1, (destinationDir.Length - destinationDir.LastIndexOf("\\") - 1));
                        string toMap = folderSourceRoot + "\\" +
                        destinationDir.Substring(folderDestinationRoot.Length + 1, destinationDir.Length - (folderDestinationRoot.Length + 2 + nameLongCatalog.Length));

                        RemoveNetworkDrive(chooseTemp2NetDrive);
                        if (AddNetwordDrive(chooseTemp2NetDrive, toMap))
                        {
                            //maxFull1recursiveName = dir.FullName;
                            maxFull1recursiveName = folderSourceRoot +
                                                    destinationDir.Substring(folderDestinationRoot.Length, destinationDir.Length - folderDestinationRoot.Length);
                            DirectoryInfo sourceSub = new DirectoryInfo(chooseTemp2NetDrive + ":\\");
                            CopySubDirectory(sourceSub, new DirectoryInfo(folderSaveToLongFile), pathToLog);

                            //sometimes without sleep not mouted net drive
                            System.Threading.Thread.Sleep(200);
                            RemoveNetworkDrive(chooseTemp2NetDrive);

                            //not required but to be sure
                            RemoveNetworkDrive(chooseTempNetDrive);
                            break;
                        }                                                
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine("Error 005 :( " + ex.Message);
                        UnmountAllTemporaryDisc();
                    }
                }
                else
                {
                    try
                    {
                        // Call CopyDirectory() recursively.
                        CopyDirectory(dir, new DirectoryInfo(destinationDir), pathToLog);
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine("Error 006 :( (Copy Directory)" + ex.Message);
                        UnmountAllTemporaryDisc();
                    }
                }
            }
        }

        /// <summary>
        /// function copy sub directory
        /// </summary>
        /// <param name="source">source folder</param>
        /// <param name="destination">destination folder</param>
        /// <param name="pathToLog">path to log</param>
        static void CopySubDirectory(DirectoryInfo source, DirectoryInfo destination, string pathToLog)
        {
            if (!destination.Exists)
            {
                if (destination.FullName.Length < 248)
                {
                    //create folder (subfolder)
                    destination.Create();
                }
                else
                {
                    Console.WriteLine("NOT COPY! " + destination.FullName +
                                      "(" +
                                      destination.FullName.Length.ToString() +
                                      " chars)", pathToLog);
                    SaveToLog("NOT COPY! " + destination.FullName +
                              "(" +
                              destination.FullName.Length.ToString() +
                              " chars)", pathToLog);
                }
            }

            // Copy all files.
            FileInfo[] files = source.GetFiles();
            foreach (FileInfo file in files)
            {
                if (File.Exists(Path.Combine(destination.FullName, file.Name)))
                {
                    string oldFile = Path.Combine(destination.FullName, file.Name);
                    string newFile = Path.Combine(source.FullName, file.Name);

                    if (!FileCompare(oldFile, newFile))
                    {
                        File.Delete(Path.Combine(destination.FullName, file.Name));
                    }
                    else
                    {
                        continue;
                    }
                }
                string usingChar = chooseTempNetDrive + ":\\" +
                                               folderDestinationRoot +
                                               "\\_toLong";
                if ((Path.Combine(destination.FullName, file.Name).Length + usingChar.Length < 256))
                {
                    //create file
                    file.CopyTo(Path.Combine(destination.FullName, file.Name));
                    Console.WriteLine(Path.Combine(source.FullName, file.Name) +
                                      " -> " +
                                      Path.Combine(destination.FullName, file.Name));
                }
                else
                {
                    Console.WriteLine("NOT COPY FILE!");
                    string pathToDisplay = Path.Combine(source.FullName, file.Name);
                    pathToDisplay = pathToDisplay.Substring(0, pathToDisplay.LastIndexOf("\\") + 1);
                    pathToDisplay = pathToDisplay.Substring(2, pathToDisplay.Length - 2); //remove letter map disc + :\
                    maxFull1recursiveName = maxFull1recursiveName.Substring(0, maxFull1recursiveName.LastIndexOf("\\")); //remove last folder
                    pathToDisplay = maxFull1recursiveName + pathToDisplay;
                    Console.WriteLine(pathToDisplay + file.Name +
                                      " (" +
                                      (pathToDisplay + file.Name).Length.ToString() +
                                      " chars)");
                    SaveToLog("NOT COPY FILE -> " +
                              pathToDisplay + file.Name +
                              " (" +
                              (pathToDisplay + file.Name).Length.ToString() +
                              " chars)", pathToLog);
                }
            }

            // Process subdirectories.
            DirectoryInfo[] dirs = source.GetDirectories();
            foreach (DirectoryInfo dir in dirs)
            {
                // Get destination directory.
                string destinationDir = Path.Combine(destination.FullName, dir.Name);

                if (destinationDir.Length > 247)
                {
                    Console.WriteLine("NOT COPY CATALOG AND HIS SUBDIRECTORES!");
                    string toCutFolderName = maxFull1recursiveName.Substring(maxFull1recursiveName.LastIndexOf("\\") + 1, maxFull1recursiveName.Length - maxFull1recursiveName.LastIndexOf("\\") - 1);
                    string toCut = chooseTemp2NetDrive + ":\\" + folderDestinationRoot + "\\_toLong\\";
                    string shortDestinationDir = destinationDir.Substring(toCut.Length - 2 + toCutFolderName.Length, destinationDir.Length - (toCut.Length - 2 + toCutFolderName.Length));
                    string pathToDisplay = maxFull1recursiveName + "\\" + shortDestinationDir;
                    Console.WriteLine(pathToDisplay + "(" + pathToDisplay.Length.ToString() + " chars)");
                    SaveToLog("NOT COPY CATALOG AND HIS SUBFOLDERS -> " + 
                              pathToDisplay + 
                              "(" + 
                              pathToDisplay.Length.ToString() + 
                              " chars)", pathToLog);
                }
                else
                {
                    try
                    {
                        // Call CopyDirectory() recursively.
                        CopySubDirectory(dir, new DirectoryInfo(destinationDir), pathToLog);
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine("Error 007 :( (Copy Sub Directory)" + ex.Message);
                        UnmountAllTemporaryDisc();
                    }
                }
            }
        }
    }
}
